﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using ReadBKF2;
using System.Threading;
using System.Text.RegularExpressions;
using System.Collections;

namespace bkfexp {
    public partial class EForm : Form {
        String fpoepn;

        public EForm(String fpoepn) {
            this.fpoepn = fpoepn;
            InitializeComponent();
        }

        private void tsbOpen_Click(object sender, EventArgs e) {
            if (ofdbkf.ShowDialog(this) == DialogResult.OK) {
                openIt(ofdbkf.FileName);
            }
        }

        BV[] albv = new BV[0];

        String fpbkf = null;
        DateTime bkfMt = DateTime.MinValue;

        bool SlowScan { get { return tsbSlowScan.Checked; } }

        class ERR_Rec : IDisposable {
            StringWriter wr = new StringWriter();

            public void Record(String scene, Exception err) {
                wr.WriteLine("『{0}』 -- {1}", err.Message, scene);
            }

            #region IDisposable メンバ

            public void Dispose() {
                if (wr.GetStringBuilder().Length != 0) {
                    MessageBox.Show("作業中に問題が発生いたしました。しかしながら、回復可能なデータ量を増やすため、無視いたしました。\n\n---\n" + wr.ToString() + "\n---", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                }
            }

            #endregion
        }

        private void openIt(string fp) {
            List<SetSel> als = new List<SetSel>();
            List<Int64> aloff;
            using (WIP wip = new WIP(this))
            using (ERR_Rec errrec = new ERR_Rec())
            using (FileStream fs = File.OpenRead(fp)) {
                fs.Position = 0;

                if (SlowScan) {
                    BkfOrdLocateSfmb4Brk2 resolver = new BkfOrdLocateSfmb4Brk2();
                    try {
                        resolver.Parse(fs);
                    }
                    catch (IOException err) { errrec.Record("SFMB読み取り中", err); }

                    aloff = resolver.aloff;
                }
                else {
                    BkfFastLocateSfmb2 resolver = new BkfFastLocateSfmb2();
                    try {
                        resolver.Parse(fs);
                    }
                    catch (IOException err) { errrec.Record("SFMB読み取り中", err); }

                    aloff = resolver.aloff;
                }

                foreach (Int64 off in aloff) {
                    BkfReadSet o = new BkfReadSet();
                    try {
                        o.Read(fs, off, BkfReadSet.K.SSet);
                    }
                    catch (IOException err) { errrec.Record("DataSet読み取り中", err); }

                    if (o.k == BkfReadSet.K.SSet) {
                        als.Add(new SetSel(o.descSet, off));
                    }
                }
            }
            using (ERR_Rec errrec = new ERR_Rec())
            using (SetForm form = new SetForm(als.ToArray())) {
                if (form.ShowDialog(this) == DialogResult.OK) {
                    fpbkf = null;
                    List<BV> albv = new List<BV>();
                    tvF.Nodes.Clear();
                    lvF.Items.Clear();

                    SetSel dataSetSel = als[form.Sel];
                    using (WIP wip = new WIP(this))
                    using (FileStream fs = File.OpenRead(fp)) {
                        fs.Position = 0;

                        bool itIs = false;
                        if (form.Fast) {
                            foreach (Int64 off in aloff) {
                                BkfReadSet o = new BkfReadSet();
                                try {
                                    o.Read(fs, off, BkfReadSet.K.SSet | BkfReadSet.K.ESet);
                                }
                                catch (IOException err) { errrec.Record("DataSet読み取り中", err); }

                                if (o.k == BkfReadSet.K.SSet && off == dataSetSel.off) {
                                    seledDataSetName = o.descSet.DataSetName;
                                    itIs = true;
                                }
                                else if (o.k == BkfReadSet.K.ESet && itIs) {
                                    albv.AddRange(o.albv);
                                    bkfMt = File.GetLastWriteTime(fpbkf = fp);
                                    MyDragDrop = null;
                                    break;
                                }
                            }
                        }
                        else {
                            foreach (Int64 off in aloff) {
                                BkfReadSet o = new BkfReadSet();
                                try {
                                    o.Read(fs, off, BkfReadSet.K.SSet);
                                }
                                catch (IOException err) { errrec.Record("DataSet読み取り中", err); }

                                if (o.k == BkfReadSet.K.SSet && off == dataSetSel.off) {
                                    seledDataSetName = o.descSet.DataSetName;

                                    BkfReadOrdSet4Brk set = new BkfReadOrdSet4Brk();
                                    try {
                                        set.Read(fs, off);
                                    }
                                    catch (IOException err) { errrec.Record("DataSet中、VOLB/DIRB/FILE読み取り中", err); }

                                    albv.AddRange(set.albv);
                                    bkfMt = File.GetLastWriteTime(fpbkf = fp);
                                    MyDragDrop = null;
                                    break;
                                }
                            }
                        }
                    }

                    {
                        foreach (BV bv in (this.albv = albv.ToArray()))
                            AddF(bv);
                    }
                }
            }
        }

        String seledDataSetName = null;

        void AddF(BV bv) {
            TreeNode tn = tvF.Nodes.Add(bv.DeviceName);
            tn.Tag = bv;

            foreach (BD bd in bv.albd) {
                AddD(tn, bd);
            }
        }

        void AddD(TreeNode tn, BD bd) {
            TreeNode tnc = tn;
            foreach (String s in bd.DirectoryName.Split('\\')) {
                if (s.Length == 0) continue;
                TreeNode tnn = null;
                foreach (TreeNode tn1 in tnc.Nodes.Find(s, false)) {
                    tnn = tn1;
                    break;
                }
                if (tnn == null) {
                    tnn = tnc.Nodes.Add(s, s);
                }
                tnc = tnn;
            }
            tnc.Tag = bd;
            tnc.ImageKey = tnc.SelectedImageKey = "A";
        }

        private void tvF_AfterSelect(object sender, TreeViewEventArgs e) {
            lvF.Items.Clear();

            switch (e.Action) {
                case TreeViewAction.ByKeyboard:
                case TreeViewAction.ByMouse:
                    BD bd = e.Node.Tag as BD;
                    if (bd != null) {
                        SelD(bd);
                    }
                    break;
            }
        }

        private void SelD(BD bd) {
            lvF.Items.Clear();
            foreach (BF bf in bd.albf) {
                BF2 bf2 = bf as BF2;
                ListViewItem lvi = new ListViewItem(bf.FileName);
                lvi.SubItems.Add((bf2 != null) ? bf2.StanSize.ToString("#,##0") : "?");
                lvi.SubItems.Add(bf.LastModificationDate.ToString("yyyy/MM/dd HH:mm:ss"));
                lvi.Tag = bf;
                lvF.Items.Add(lvi);
            }
        }

        private void tsbExpcsv_Click(object sender, EventArgs e) {
            if (sfdcsv.ShowDialog(this) == DialogResult.OK) {
                using (WIP wip = new WIP(this))
                using (StreamWriter wr = new StreamWriter(sfdcsv.FileName, false, Encoding.Default)) {
                    int rows = 0;
                    foreach (BV bv in albv) {
                        foreach (BD bd in bv.albd) {
                            String dir = Path.Combine(bv.DeviceName, bd.DirectoryName);
                            foreach (BF bf in bd.albf) {
                                String fp = Path.Combine(dir, bf.FileName);
                                BF2 bf2 = bf as BF2;
                                wr.WriteLine("\"" + fp.Replace("\"", "\"\"") + "\"" + "," + (bf2 != null ? bf2.StanSize.ToString() : "?") + "," + "\"" + bf.LastModificationDate.ToString("yyyy/MM/dd HH:mm:ss") + "\"");
                                rows++;
                            }
                        }
                    }
                    wr.Close();
                    MessageBox.Show(this, "件数=" + rows.ToString("#,##0"), "報告", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
            }
        }

        BV Seledbv {
            get {
                TreeNode tn = tvF.SelectedNode;
                while (tn != null) {
                    if (tn.Parent == null) {
                        return tn.Tag as BV;
                    }
                    tn = tn.Parent;
                }
                return null;
            }
        }

        BD Seledbd {
            get {
                TreeNode tn = tvF.SelectedNode;
                if (tn == null) return null;
                return tn.Tag as BD;
            }
        }

        BD[] Seledalbd {
            get {
                TreeNode tn = tvF.SelectedNode;
                Queue<TreeNode> q = new Queue<TreeNode>();
                q.Enqueue(tn);
                List<BD> albd = new List<BD>();
                while (q.Count != 0) {
                    TreeNode tnc = q.Dequeue();
                    BD bd = tnc.Tag as BD;
                    if (bd != null) albd.Add(bd);
                    foreach (TreeNode tncc in tnc.Nodes) q.Enqueue(tncc);
                }
                return albd.ToArray();
            }
        }

        BF[] Seledalbf {
            get {
                List<BF> albf = new List<BF>();
                foreach (ListViewItem lvi in lvF.SelectedItems) {
                    BF bf = lvi.Tag as BF;
                    if (bf != null) albf.Add(bf);
                }
                return albf.ToArray();
            }
        }

        BF2[] Seledalbf2 {
            get {
                List<BF2> albf = new List<BF2>();
                foreach (ListViewItem lvi in lvF.SelectedItems) {
                    BF2 bf = lvi.Tag as BF2;
                    if (bf != null) albf.Add(bf);
                }
                return albf.ToArray();
            }
        }

        class TUt {
            internal static bool IsMine(IDataObject data, String idMe) {
                String s = data.GetData("idMe") as String;
                if (s != null && s == idMe)
                    return true;
                return false;
            }
        }

        private void EForm_DragEnter(object sender, DragEventArgs e) {
            e.Effect = e.AllowedEffect & ((e.Data.GetDataPresent(DataFormats.FileDrop) && !TUt.IsMine(e.Data, idMe)) ? DragDropEffects.Copy : DragDropEffects.None);
        }

        SynchronizationContext Sync;

        private void EForm_DragDrop(object sender, DragEventArgs e) {
            String[] alfp = e.Data.GetData(DataFormats.FileDrop) as String[];
            if (alfp == null) return;
            foreach (String fp in alfp) {
                Sync.Post(delegate(object state) {
                    openIt(fp);
                }, null);
                break;
            }
        }

        String ExDir {
            get {
                String fp = Path.GetTempFileName();
                File.Delete(fp);
                Directory.CreateDirectory(fp);
                return fp;
            }
        }

        String GetSeledDirTree() {
            TreeNode tn = tvF.SelectedNode;
            String dir = "";
            while (tn != null) {
                TreeNode tncur = tn;
                tn = tn.Parent;
                if (tn != null) {
                    if (dir.Length != 0)
                        dir = "\\" + dir;
                    dir = tncur.Text + dir;
                }
            }
            return dir;
        }

        String GetSeledOneDir() {
            TreeNode tn = tvF.SelectedNode;
            if (tn != null)
                return FUt.Clean(tn.Text);
            return "";
        }

        class FUt {
            public static string Clean(string fn) {
                return fn.Replace(":", "");
            }
        }

        List<String> ExtractSeledFiles(String workdir) {
            BV bv = Seledbv;
            if (bv == null) return null;
            BD bd = Seledbd;
            if (bd == null) return null;
            BF2[] albf = Seledalbf2;
            if (albf.Length == 0) return null;

            if (File.GetLastWriteTime(fpbkf) != bkfMt) {
                MessageBox.Show(this, "更新日付が読み取った時と比べて変化しています。続行できません。", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return null;
            }

            List<string> alGo = new List<string>();
            String dirt = workdir; // Path.Combine(workdir, GetSeledDirTree());
            int x = 0, cx = albf.Length;
            if (cx < 1) return new List<string>();
            using (FileStream fs = File.OpenRead(fpbkf)) {
                using (ExWIP wip = ExWIP.Show(this)) {
                    foreach (BF2 bf in albf) {
                        wip.SetWork(x, cx);
                        String fpTo = Path.Combine(dirt, bf.FileName);
                        Directory.CreateDirectory(Path.GetDirectoryName(fpTo));
                        new BkfFileExposure(fs, bf).Expose(fpTo);
                        alGo.Add(fpTo);
                        x++;
                    }
                }
            }
            return alGo;
        }

        SortedDictionary<String, BF2> CollectfrmSeledDir() {
            SortedDictionary<String, BF2> dict = new SortedDictionary<String, BF2>();
            TreeNode tn = tvF.SelectedNode;
            Stack<KeyValuePair<String, TreeNode>> s = new Stack<KeyValuePair<string, TreeNode>>();
            s.Push(new KeyValuePair<string, TreeNode>("", tn));
            List<BD> albd = new List<BD>();
            while (s.Count != 0) {
                KeyValuePair<string, TreeNode> kv = s.Pop();
                TreeNode tnc = kv.Value;
                BD bd = tnc.Tag as BD;
                if (bd != null) {
                    dict[(kv.Key).TrimStart('\\')] = null;
                    foreach (BF bf in bd.albf) {
                        BF2 bf2 = bf as BF2;
                        if (bf2 != null) {
                            dict[((String)(kv.Key + "\\" + bf2.FileName)).TrimStart('\\')] = bf2;
                        }
                    }
                }
                foreach (TreeNode tncc in tnc.Nodes) {
                    s.Push(new KeyValuePair<string, TreeNode>(((String)(kv.Key + "\\" + tncc.Text)).TrimStart('\\'), tncc));
                }
            }
            return dict;
        }

        private void tsmiExpFile_Click(object sender, EventArgs e) {
            BV bv = Seledbv;
            if (bv == null) return;
            BD bd = Seledbd;
            if (bd == null) return;
            BF[] albf = Seledalbf;

            if (fbdexp.ShowDialog(this) == DialogResult.OK) {
                List<String> alGo = ExtractSeledFiles(fbdexp.SelectedPath);
                if (alGo != null) {
                    if (alGo.Count != 0) {
                        MessageBox.Show(this, "成功しました。", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
                    }
                    else {
                        MessageBox.Show(this, "ファイルは含まれませんでした。", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
                    }
                }
                else {
                    MessageBox.Show(this, "失敗しました。", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                }
            }
        }

        private void tsmiExpDir_Click(object sender, EventArgs e) {
            BV bv = Seledbv;
            if (bv == null) return;
            BD[] albd = Seledalbd;
            if (albd.Length == 0) return;

            if (fbdexp.ShowDialog(this) == DialogResult.OK) {
                String dir = ExtractSeledDirFiles(fbdexp.SelectedPath, true);
                if (dir != null) {
                    if (dir != String.Empty) {
                        MessageBox.Show(this, "成功しました。", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
                    }
                    else {
                        MessageBox.Show(this, "ファイルは含まれませんでした。", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
                    }
                }
                else {
                    MessageBox.Show(this, "失敗しました。", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="workdir"></param>
        /// <param name="forceOverwrite"></param>
        /// <returns>null, String.Empty or path</returns>
        String ExtractSeledDirFiles(String workdir, bool forceOverwrite) {
            BD[] albd = Seledalbd;
            if (albd.Length == 0) return null;

            if (File.GetLastWriteTime(fpbkf) != bkfMt) {
                MessageBox.Show(this, "更新日付が読み取った時と比べて変化しています。続行できません。", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return null;
            }

            String dirt = Path.Combine(workdir, GetSeledOneDir());
            SortedDictionary<String, BF2> dict = CollectfrmSeledDir();
            int cx = dict.Count;
            if (cx < 1) return String.Empty;

            using (FileStream fs = File.OpenRead(fpbkf)) {
                using (VisuForm form = new VisuForm(dict, dirt, fs)) {
                    form.ShowDialog();
                }
            }

            return dirt;
        }

        DataObject dataObj = null;

        DataObject MyDragDrop {
            get {
                return dataObj;
            }
            set {
                dataObj = value;
                tsbDrag.Visible = dataObj != null;
            }
        }

        String idMe = Guid.NewGuid().ToString();

        private void tvF_ItemDrag(object sender, ItemDragEventArgs e) {
            String dirt = ExtractSeledDirFiles(ExDir, false);
            if (dirt == null || dirt == String.Empty) return;

            MyDragDrop = new DataObject(DataFormats.FileDrop, new String[] { dirt });
            MyDragDrop.SetData("idMe", idMe);

            lvF.DoDragDrop(MyDragDrop, DragDropEffects.Copy | DragDropEffects.Move);
        }

        private void lvF_ItemDrag(object sender, ItemDragEventArgs e) {
            List<String> alGo = ExtractSeledFiles(ExDir);
            if (alGo == null || alGo.Count == 0) return;

            MyDragDrop = new DataObject(DataFormats.FileDrop, alGo.ToArray());
            MyDragDrop.SetData("idMe", idMe);

            lvF.DoDragDrop(MyDragDrop, DragDropEffects.Copy | DragDropEffects.Move);
        }

        private void tsbDrag_MouseDown(object sender, MouseEventArgs e) {
            lvF.DoDragDrop(MyDragDrop, DragDropEffects.Copy | DragDropEffects.Move);
        }

        int FindLv(IKwut kw, FindDir fd) {
            int i = 0;
            if (fd == FindDir.Next) {
                // next
                foreach (int t in lvF.SelectedIndices) {
                    i = t + 1;
                    break;
                }
            }
            else if (fd == FindDir.Prev) {
                // prev
                i = lvF.Items.Count - 1;
                foreach (int t in lvF.SelectedIndices) {
                    i = t - 1;
                    break;
                }
            }
            for (; 0 <= i && i < lvF.Items.Count; i += (fd != FindDir.Prev ? +1 : -1)) {
                String fn = lvF.Items[i].Text;
                if (kw.IsMatch(fn))
                    return i;
            }
            return -1;
        }

        enum FindDir {
            Entire, Next, Prev,
        }

        private void tsbSearchNow_Click(object sender, EventArgs e) {
            FindDir fd = ((sender != tsbSearchPrev) ? FindDir.Next : FindDir.Prev);
            IKwut kw;
            try {
                kw = tsbRex.Checked ? (IKwut)new Rexut(tstbkw.Text) : new Kwut(tstbkw.Text);
            }
            catch (Exception err) {
                MessageBox.Show(this, "検索に失敗しました。\n\n" + err.ToString(), Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }

            int i = FindLv(kw, fd);
            if (i < 0) {
                TreeNode tn = FindTv(kw, fd);
                if (tn != null) {
                    tvF.SelectedNode = tn;
                    tvF_AfterSelect(sender, new TreeViewEventArgs(tn, TreeViewAction.ByMouse));
                    i = FindLv(kw, fd);
                }
            }
            if (i >= 0) {
                if (fd != FindDir.Prev) {
                    for (int x = 0; x <= i; x++) {
                        if (lvF.Items[x].Selected)
                            lvF.Items[x].Selected = false;
                    }
                }
                else {
                    for (int x = lvF.Items.Count - 1; x >= i; x--) {
                        if (lvF.Items[x].Selected)
                            lvF.Items[x].Selected = false;
                    }
                }
                ListViewItem lvi = lvF.Items[i];
                lvi.Focused = true;
                lvi.Selected = true;
                lvi.EnsureVisible();
            }
            else {
                MessageBox.Show(this, "有りません。", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }

        class NUt {
            public static TreeNode FindNext(TreeNode tn) {
                TreeNode tnx;
                foreach (TreeNode tnc in tn.Nodes) {
                    return tnc;
                }
                while (true) {
                    tnx = tn.NextNode;
                    if (tnx != null)
                        return tnx;
                    tnx = tn.Parent;
                    if (tnx == null)
                        return null;
                    tn = tnx;
                }
            }

            public static TreeNode FindPrev(TreeNode tn) {
                TreeNode tnx = tn.PrevNode;
                if (tnx != null) {
                    TreeNodeCollection tnc = tnx.Nodes;
                    if (tnc.Count != 0) {
                        return tnc[tnc.Count - 1];
                    }
                    return tnx;
                }
                return tn.Parent;
            }
        }

        TreeNode FindTv(IKwut kw, FindDir fd) {
            TreeNode tn = tvF.SelectedNode;
            if (tn == null) {
                foreach (TreeNode tn1 in tvF.Nodes) {
                    tn = tn1;
                    break;
                }
            }
            if (tn != null) {
                while (true) {
                    tn = (fd != FindDir.Prev)
                        ? NUt.FindNext(tn)
                        : NUt.FindPrev(tn);
                    if (tn == null)
                        break;
                    BD bd = tn.Tag as BD;
                    if (bd != null) {
                        foreach (BF bf in bd.albf) {
                            if (kw.IsMatch(bf.FileName)) {
                                return tn;
                            }
                        }
                    }
                }
            }
            return null;
        }

        interface IKwut {
            bool IsMatch(String s);
        }

        class Rexut : IKwut {
            Regex rex;

            public Rexut(String rex) {
                this.rex = new Regex(rex, RegexOptions.IgnoreCase | RegexOptions.Singleline);
            }

            #region IKwut メンバ

            public bool IsMatch(String s) {
                return rex.IsMatch(s);
            }

            #endregion
        }

        class Kwut : IKwut {
            String[] alkw;

            public Kwut(String kws) {
                alkw = Regex.Replace(kws, "\\s+", " ").Trim().Split(' ');
            }

            public bool IsMatch(String s) {
                if (alkw.Length == 0) return false;
                foreach (String kw in alkw) {
                    if (s.IndexOf(kw, StringComparison.CurrentCultureIgnoreCase) < 0)
                        return false;
                }
                return true;
            }
        }

        private void EForm_Load(object sender, EventArgs e) {
            Sync = SynchronizationContext.Current;

            this.Text = this.Text.Replace("(*)", new Version(Application.ProductVersion).ToString(3));

            if (fpoepn != null) {
                Show();
                openIt(fpoepn);
            }
        }

        class SortStrInt : IComparer<ListViewItem>, IComparer {
            int i;
            int ord;

            public SortStrInt(int i, bool asc) {
                this.i = i;
                this.ord = asc ? 1 : -1;
            }

            #region IComparer<ListViewItem> メンバ

            public int Compare(ListViewItem x, ListViewItem y) {
                bool fx = i < x.SubItems.Count;
                bool fy = i < y.SubItems.Count;
                if (fx && fy) {
                    Int64 vx;
                    Int64 vy;
                    bool px = Int64.TryParse(x.SubItems[i].Text.Replace(",", ""), out vx);
                    bool py = Int64.TryParse(y.SubItems[i].Text.Replace(",", ""), out vy);
                    if (px && py) {
                        return ord * vx.CompareTo(vy);
                    }
                    return ord * px.CompareTo(py);
                }
                return ord * fx.CompareTo(fy);
            }

            #endregion

            #region IComparer メンバ

            public int Compare(object x, object y) { return Compare((ListViewItem)x, (ListViewItem)y); }

            #endregion
        }

        class SortStr : IComparer<ListViewItem>, IComparer {
            int i;
            int ord;

            public SortStr(int i, bool asc) {
                this.i = i;
                this.ord = asc ? 1 : -1;
            }

            #region IComparer<ListViewItem> メンバ

            public int Compare(ListViewItem x, ListViewItem y) {
                bool fx = i < x.SubItems.Count;
                bool fy = i < y.SubItems.Count;
                return ord * ((fx && fy)
                    ? x.SubItems[i].Text.CompareTo(y.SubItems[i].Text)
                    : fx.CompareTo(fy)
                    );
            }

            #endregion

            #region IComparer メンバ

            public int Compare(object x, object y) { return Compare((ListViewItem)x, (ListViewItem)y); }

            #endregion
        }

        private void lvF_ColumnClick(object sender, ColumnClickEventArgs e) {
            switch (e.Column) {
                case 0: lvF.ListViewItemSorter = new SortStr(e.Column, 0 == (ModifierKeys & Keys.Shift)); break;
                case 1: lvF.ListViewItemSorter = new SortStrInt(e.Column, 0 != (ModifierKeys & Keys.Shift)); break;
                case 2: lvF.ListViewItemSorter = new SortStr(e.Column, 0 != (ModifierKeys & Keys.Shift)); break;
            }
        }

        private void tsbSlowScan_Click(object sender, EventArgs e) {

        }
    }

    public class SetSel {
        public SetDesc descSet;
        public Int64 off;

        public SetSel(SetDesc descSet, Int64 off) {
            this.descSet = descSet;
            this.off = off;
        }

        public override string ToString() {
            return string.Format("{0}#{1}", descSet.DataSetName, off);
        }
    }
}